package com.fwtai.tool;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fwtai.bean.PageFormData;
import com.fwtai.bean.UploadFile;
import com.fwtai.bean.UploadObject;
import com.fwtai.config.ConfigFile;
import com.fwtai.config.DataSourceContextHolder;
import com.fwtai.config.FlagToken;
import com.fwtai.config.LocalLoginUser;
import com.fwtai.config.LocalUserId;
import com.fwtai.config.RenewalToken;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 客户端请求|服务器端响应工具类
 * @作者 田应平
 * @版本 v1.0
 * @创建时间 2017年1月11日 19:20:50
 * @QQ号码 444141300
 * @主页 http://www.fwtai.com
 */
public final class ToolClient{

  private static Logger logger = LoggerFactory.getLogger(ToolClient.class);

  /**
   * 生成简单类型json字符串,仅用于查询返回,客户端只需判断code是否为200才操作,仅用于查询操作,除了list集合之外都可以用data.map获取数据,list的是data.listData,字符串或数字对应是obj
   * @作者 田应平
   * @注意 如果传递的是List则在客户端解析listData的key值,即data.listData;是map或HashMap或PageFormData解析map的key值,即data.map;否则解析obj的key值即data.obj或data.map
   * @用法 解析后判断data.code == AppKey.code.code200 即可
   * @返回值类型 返回的是json字符串,内部采用JSONObject封装,不可用于redis缓存value
   * @创建时间 2017年1月11日 上午10:27:53
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static String queryJson(final Object object){
    return CompletableFuture.supplyAsync(()->{
      if(object == null || String.valueOf(object).replaceAll("\\s*", "").length() == 0){
        return queryEmpty();
      }
      if(object instanceof Map<?,?>){
        final Map<?,?> map = (Map<?,?>) object;
        if(map.size() == 0){
          return queryEmpty();
        }else {
          final JSONObject json = new JSONObject(3);
          json.put(ConfigFile.data,map);
          longFilter(json);//上一行put对应的key和value,所以需要处理map里含有Long类型
          json.put(ConfigFile.code,ConfigFile.code200);
          json.put(ConfigFile.msg,ConfigFile.msg200);
          return json.toJSONString();
        }
      }
      final JSONObject json = new JSONObject(4);
      if(object instanceof List<?>){
        final List<?> list = (List<?>) object;
        if(list.size() == 0){
          return queryEmpty();
        }else {
          final Object objMap = list.get(0);
          if(objMap instanceof Map<?,?>){
            final Map<?,?> map = (Map<?,?>) objMap;
            if(map.size() == 0){
              return queryEmpty();
            }
          }
          json.put(ConfigFile.data,object);
          longFilter(json);
          json.put(ConfigFile.code,ConfigFile.code200);
          json.put(ConfigFile.msg,ConfigFile.msg200);
          json.put(ConfigFile.record,list.size());
          return json.toJSONString();
        }
      }
      json.put(ConfigFile.data,object);
      longFilter(json);
      json.put(ConfigFile.code,ConfigFile.code200);
      json.put(ConfigFile.msg,ConfigFile.msg200);
      return json.toJSONString();
    }).join();
  }

  /**
   * 查询时得到的数据为空返回的json字符串
   * @作者 田应平
   * @返回值类型 String
   * @创建时间 2017年1月11日 下午9:40:21
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  private static String queryEmpty(){
    return createJson(ConfigFile.code201,ConfigFile.msg201);
  }

  /**
   * 查询时得到的数据为空返回的json字符串
   * @param msg 指定提示信息
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2021/9/1 10:40
  */
  public static String queryEmpty(final String msg){
    return createJson(ConfigFile.code201,msg);
  }

  /**尚未绑定账号,提示绑定*/
  public static String wechat(final String openid){
    final JSONObject json = new JSONObject(3);
    json.put(ConfigFile.code,ConfigFile.code198);
    json.put("openid",openid);
    json.put(ConfigFile.msg,"尚未绑定账号,请绑定");
    return json.toJSONString();
  }

  public static String json198(final Object data){
    final JSONObject json = new JSONObject(2);
    json.put(ConfigFile.data,data);
    longFilter(json);//注意此处
    json.put(ConfigFile.code,ConfigFile.code198);
    return json.toJSONString();
  }

  //特定code处理
  public static String jsonCode198(final String msg){
    final JSONObject json = new JSONObject(2);
    json.put(ConfigFile.code,ConfigFile.code198);
    json.put(ConfigFile.msg,msg);
    return json.toJSONString();
  }

  /**
   * 生成json字符串对象,直接采用JSONObject封装,执行效率会更高;适用于为增、删、改操作时,判断当前的rows是否大于0来确定是否操作成功,一般在service调用,偷懒的人可以使用本方法
   * @param rows 执行后受影响的行数
   * @用法 解析后判断data.code == AppKey.code.code200即可操作
   * @作者 田应平
   * @创建时间 2016年12月25日 下午5:44:23
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String executeRows(final int rows){
    return rows > 0 ? jsonData(ConfigFile.msg200,rows) : createJsonFail();
  }

  /**
   * 操作成功生成json字符串对象,失败信息是ConfigFile.msg199,直接采用JSONObject封装,执行效率会更高;适用于为增、删、改操作,一般在service调用
   * @param
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020/1/19 11:31
  */
  public static String executeRows(final int rows,final String success){
    return rows > 0 ? jsonData(success,rows) :createJsonFail();
  }

  /**
   * 生成自定义的json对象,直接采用JSONObject封装,执行效率会更高;适用于为增、删、改操作,一般在service调用
   * @param rows 执行后受影响的行数
   * @param success 执行成功的提示消息
   * @param failure 执行失败的提示消息
   * @作者 田应平
   * @创建时间 2016年12月25日 下午5:50:22
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String executeRows(final int rows,final String success,final String failure){
    return rows > 0 ? jsonData(success,rows) : createJsonFail(failure);
  }

  /**
   * code=200的成功json数据格式
   * @param msg code=200时提示的信息
   * @param data key的data的数据对象
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2021/3/31 14:28
  */
  private static String jsonData(final String msg,final int data){
    final JSONObject json = new JSONObject(3);
    json.put(ConfigFile.data,data);
    json.put(ConfigFile.code,ConfigFile.code200);
    json.put(ConfigFile.msg,msg);
    return json.toJSONString();
  }

  /**
   * 生成json格式字符串,code和msg的key是固定的,直接采用JSONObject封装,执行效率会更高,用于增、删、改、查操作,一般在service层调用
   * @作者 田应平
   * @返回值类型 返回的是json字符串,内部采用JSONObject封装
   * @用法 解析后判断data.code == AppKey.code.code200即可处理操作
   * @创建时间 2016年12月25日 18:11:16
   * @QQ号码 444141300
   * @param code 相关参数协议
   * @主页 http://www.fwtai.com
  */
  public static String createJson(final int code,final String msg){
    final JSONObject json = new JSONObject(2);
    json.put(ConfigFile.code,code);
    json.put(ConfigFile.msg,msg);
    return json.toJSONString();
  }

  /**
   * 生成json格式字符串,直接采用JSONObject封装,执行效率会更高,用于增、删、改、查操作,一般在service层调用
   * @作者 田应平
   * @返回值类型 返回的是json字符串,内部采用JSONObject封装
   * @用法 解析后判断data.code == AppKey.code.code200即可处理操作
   * @创建时间 2018年7月3日 09:20:31
   * @QQ号码 444141300
   * @param map 相关参数协议
   * @主页 http://www.fwtai.com
  */
  public static String createJson(final Map<String,Object> map){
    return new JSONObject(map).toJSONString();
  }

  /**
   * 生成code为199的json格式数据且msg是提示信息
   * @param
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2021/6/30 9:38
  */
  public static String createJsonFail(){
    return createJsonFail(ConfigFile.msg199);
  }

  /**
   * 生成code为199的json格式数据且msg是提示信息
   * @param msg 提示信息
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2019/7/29 15:00
  */
  public static String createJsonFail(final String msg){
    return createJson(ConfigFile.code199,msg);
  }

  /**仅使用于get请求时的导出或下载*/
  public static String createTitleMsg(final String msg){
    return ConfigFile.title+msg;
  }

  /**
   * 生成code为200的json格式数据且msg是提示信息
   * @param msg 提示信息
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2019/7/29 15:00
  */
  public static String createJsonSuccess(final String msg){
    return createJson(ConfigFile.code200,msg);
  }

  /**
   * 生成code为200的json格式数据且msg是提示信息
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2021/6/30 9:40
  */
  public static String createJsonSuccess(){
    return createJsonSuccess(ConfigFile.msg200);
  }

  /**
   * 验证必要的参数字段是否为空的返回json格式专用,先调用方法validateField()返回值false后再直接调用本方法返回json字符串
   * @作者 田应平
   * @返回值类型 返回的是json字符串,内部采用JSONObject封装
   * @创建时间 2017年1月11日 下午7:38:48
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static String jsonValidateField(){
    return jsonValidateField(ConfigFile.msg202);
  }

  /**
   * 验证必要的参数字段是否为空的返回json格式专用,先调用方法validateField()返回值false后再直接调用本方法返回json字符串
   * @param msg 提示信息
   * @作者 田应平
   * @返回值类型 返回的是json字符串,内部采用JSONObject封装
   * @创建时间 2017年1月11日 下午7:38:48
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static String jsonValidateField(final String msg){
    return createJson(ConfigFile.code202,msg);
  }

  /**
   * 必填必传参数
   * @param
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2021/8/4 18:56
  */
  public static String required(final String value){
    if(value == null || value.length() == 0){
      return jsonValidateField();
    }else{
      return null;
    }
  }

  /**
   * 验证传入的值是否有值
   * @param
   * @用法 ：final String verifyField = ToolClient.validateField(kid,total); if(verifyField != null) return verifyField;
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020/5/22 11:30
  */
  public static String validateField(final String... fields){
    if(fields == null || fields.length == 0){
      return jsonValidateField();
    }
    for(int i = fields.length - 1; i >= 0; i--){
      final String value = fields[i];
      if(value == null || value.length() == 0) return jsonValidateField();
    }
    return null;
  }

  /**
   * 验证必要的字段是否为空,service层或controller层都适用,如果返回为 null 则验证成功,否则失败;适用于增、删、改、查操作!
   * @fields 需要验证的form字段
   * @用法1 final String validate = ToolClient.validateField(params,"kid");if(validate != null)return validate;
   * @用法2 final String validate = ToolClient.validateField(params,new String[]{"id"});if(validate != null)return validate;
   * @作者 田应平
   * @返回值类型 返回的是json字符串,内部采用JSONObject封装,如果返回为 null 则验证成功!
   * @创建时间 2017年2月23日 下午10:10:34
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static String validateField(final Map<String,?> params,final String... fields){
    if(params == null || params.size() == 0) return jsonValidateField();
    for (final String value : fields){
      final Object object = params.get(value);
      if(object == null){
        logger.warn(value+"参数的值为空");
        return jsonValidateField();
      }else{
        final boolean bl = checkNull(String.valueOf(object));
        if(bl){
          return jsonValidateField();
        }
      }
    }
    return null;
  }

  /**
   * 验证必填字段,线程安全
   * @用法1 final String validate = ToolClient.validateForm(params,"kid");if(validate != null)return validate;
   * @用法2 final String validate = ToolClient.validateForm(params,new String[]{"id"});if(validate != null)return validate;
   * @param
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2019/11/2 18:31
  */
  public static String validateForm(final ConcurrentHashMap<String,String> formData,final String[] fields){
    if(ToolString.isBlank(formData) || ToolString.isBlank(fields)){
      return jsonValidateField();
    }
    boolean flag = false;
    for(final String p : fields){
      if(ToolString.isBlank(formData.get(p))){
        flag = true;
        break;
      }
    }
    if(flag)return jsonValidateField();
    return null;
  }

  private static String jsonValidateInteger(){
    return createJson(ConfigFile.code199,"参数类型有误");
  }

  /**
   * 验证所输入的数据是否是Integer类型,先验证是否必填后才调用本方法,service层或controller层都适用
   * @用法1 final String fieldInteger = ToolClient.validateInteger(formData,"type");if(fieldInteger != null)return fieldInteger;
   * @用法2 final String fieldInteger = ToolClient.validateInteger(formData,new String[]{"category","subset","type"});if(fieldInteger != null)return fieldInteger;
   * @param
   * @作者 田应平
   * @QQ 444141300`
   * @创建时间 2020/4/2 13:04
  */
  public static String validateInteger(final Map<String,?> params,final String... fields){
    if(params == null || params.size() == 0) return jsonValidateInteger();
    for(int i = 0; i < fields.length;i++){
      try {
        final Object o = params.get(fields[i]);
        if(o != null){
          final String value = String.valueOf(String.valueOf(o));
          if(value.equalsIgnoreCase("null") || value.equalsIgnoreCase("undefined") || value.equals("_") || value.length() == 0)return jsonValidateInteger();
          Integer.parseInt(value);
        }else{
          return jsonValidateInteger();
        }
      } catch (final Exception e) {
        return jsonValidateInteger();
      }
    }
    return null;
  }

  /**
   * 生成|计算总页数
   * @作者 田应平
   * @返回值类型 Integer
   * @创建时间 2016年12月2日 下午1:20:53
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static Integer totalPage(final Integer total,final Integer pageSize){
    return (total%pageSize) == 0 ? (total/pageSize):(total/pageSize)+1; //总页数
  }

  /**
   * 生成json对象
   * @param map
   * @作者 田应平
   * @返回值类型 String
   * @创建时间 2017年7月30日 22:47:24
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String jsonObj(final Map<String, Object> map){
    return JSON.toJSONString(map);
  }

  /**
   * 生成json数组
   * @param listData
   * @return
   * @作者 田应平
   * @返回值类型 String
   * @创建时间 2017年1月12日 下午9:28:55
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String jsonArray(final List<?> listData){
    return JSON.toJSONString(listData);
  }


  /**
   * 用于生成出现异常信息时的json固定code:204字符串提示,返回给controller层调用,一般在service层调用
   * @作者 田应平
   * @返回值类型 String,内部采用JSONObject封装,msg 为系统统一的‘服务出现错误’
   * @创建时间 2017年1月10日 21:40:23
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static String exceptionJson(){
    return exceptionJson(ConfigFile.msg204);
  }

  /**
   * 用于生成出现异常信息时的json固定code:204字符串提示,返回给controller层调用,一般在service层调用
   * @param msg 自定义提示的异常信息
   * @作者 田应平
   * @返回值类型 String,内部采用JSONObject封装
   * @创建时间 2017年1月10日 21:40:23
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static String exceptionJson(final String msg){
    return createJson(ConfigFile.code204,msg);
  }

  public static String exceptionJson(final String msg,final Throwable throwable){
    logger.error(throwable.getMessage());
    return createJson(ConfigFile.code204,msg);
  }

  public static String exceptionJson(final Throwable throwable){
    logger.error(throwable.getMessage());
    return exceptionJson();
  }

  /**
   * 返回给客户端服务出现错误的提示信息,已返回给客户端,只能在controller层调用,用于增、删、改、查操作的异常返回给客户端
   * @注意 不能在service层调用
   * @作者 田应平
   * @创建时间 2016年12月25日 下午5:07:16
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static void responseException(final HttpServletResponse response){
    responseJson(exceptionJson(),response);
  }

  /**
   * 返回给客户端服务出现错误的提示信息,已返回给客户端,只能在controller层调用,用于增、删、改、查操作的异常返回给客户端
   * @param msg 自定义提示的异常信息
   * @注意 不能在service层调用
   * @作者 田应平
   * @创建时间 2016年12月25日 下午5:07:16
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static void responseException(final HttpServletResponse response,final String msg){
    responseJson(exceptionJson(msg),response);
  }

  /**
   * 通用的响应json返回json对象,只能在是controller层调用
   * @param json json对象
   * @注意 不能在service层调用
   * @作者 田应平
   * @创建时间 2016年8月18日 17:53:18
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static void responseJson(final String json){
    try {
      responseJson(json,getResponse());
    } catch (final Exception ignored) {}
  }

  /**
   * 通用的响应json返回json对象,只能在是controller层调用
   * @param json json对象
   * @param response
   * @注意 不能在service层调用
   * @作者 田应平
   * @创建时间 2016年8月18日 17:53:18
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static void responseJson(final String json,final HttpServletResponse response){
    response.setContentType("text/html;charset=utf-8");
    response.setHeader("Cache-Control","no-cache");
    PrintWriter writer = null;
    try {
      writer = response.getWriter();
      if(json == null){
        writer.write(createJson(ConfigFile.code201,ConfigFile.msg201));
      }else{
        final String token = RenewalToken.get();
        if(token != null){
          writer.write(JSON.parseObject(json).fluentPut("renewal","token").toJSONString());
        }else{
          writer.write(json);
        }
      }
      writer.flush();
    }catch (final IOException e){
      logger.error("类ToolClient的方法responseJson出现异常",e.getMessage());
    }finally{
      removeAll();
      if(writer != null){
        writer.close();
        writer = null;
      }
    }
  }

  public static void removeAll(){
    FlagToken.remove();
    LocalUserId.remove();
    DataSourceContextHolder.remove();
    LocalLoginUser.remove();
    RenewalToken.remove();
  }

  /**
   * 利用反射从表单构建实体对象,做业务之前先做判断是否为空再处理业务
   * @用法 final UserEntity user = ToolEntity.getEntity(request,UserEntity.class);
   * @param request
   * @param clszz 目标实体
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2021/5/30 11:50
  */
  public static <T> T getEntity(final HttpServletRequest request,final Class<T> clszz){
    final Field[] fields = clszz.getDeclaredFields();//获取所有的属性
    try {
      final T object = clszz.newInstance();//实例化对象
      for(final Field field : fields){
        field.setAccessible(true);
        final String type = field.getType().getSimpleName();
        final String name = field.getName();
        final String value = request.getParameter(name);
        switch (type){
          case "Float":
            if(value != null && value.length() >0){
              field.set(object,Float.parseFloat(value));
            }
            break;
          case "Double":
            if(value != null && value.length() >0){
              field.set(object,Double.parseDouble(value));
            }
            break;
          case "Integer":
            if(value != null && value.length() >0){
              field.set(object,Integer.parseInt(value));
            }
            break;
          case "Long":
            if(value != null && value.length() >0){
              field.set(object,Long.parseLong(value));
            }
            break;
          default:
            if(value != null && value.length() >0){
              field.set(object,value);
            }
            break;
        }
      }
      return object;
    } catch (final Exception e){
      e.printStackTrace();
    }
    return null;
  }

  public static HttpServletRequest getRequest(){
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  }

  public static HttpServletResponse getResponse(){
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
  }

  /**
   * 指定文件路径下载
   * @param filePath 文件物理路径
   * @param response
   * @作者 田应平
   * @创建时间 2015-10-17 下午6:01:36
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static boolean download(final String filePath,final HttpServletResponse response){
    try {
      // filePath是指欲下载的文件的全路径。
      final File file = new File(filePath);
      if(!file.exists()){
        logger.info("类ToolClient.java下的方法download():文件不存在");
        return false;
      }
      // 取得文件名。
      final String filename = file.getName();
      final int node = filename.lastIndexOf(".")+1;
      // 取得文件的后缀名。
      final String ext = filename.substring(node);
      // 以流的形式下载文件。
      final InputStream fis = new BufferedInputStream(new FileInputStream(file));
      final byte[] buffer = new byte[fis.available()];
      fis.read(buffer);
      fis.close();
      // 清空response
      response.reset();
      // 设置response的Header
      response.addHeader("Content-Disposition", "attachment;filename="+ new String((filename.substring(0,node) + ext).getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1));
      response.addHeader("Content-Length",String.valueOf(file.length()));
      OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
      response.setContentType("application/octet-stream");
      toClient.write(buffer);
      toClient.flush();
      toClient.close();
      return true;
    } catch (IOException ex){
      ex.printStackTrace();
      logger.error("类ToolClient.java下的方法download():出现异常",ex);
      return false;
    }
  }

  public static boolean download(final byte[] buffer,final String fileName,final HttpServletResponse response){
    try {
      response.reset();
      response.addHeader("Content-Disposition", "attachment;filename="+ new String(fileName.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1));
      response.addHeader("Content-Length",String.valueOf(buffer.length));
      OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
      response.setContentType("application/octet-stream");
      toClient.write(buffer);
      toClient.flush();
      toClient.close();
      return true;
    } catch (final IOException ex){
      return false;
    }
  }

  /**
   * 获取项目所在的物理路径,推荐使用,但不适合jar运行的项目,仅能通过配置固定目录
   * @param request
   * @作者 田应平
   * @创建时间 2017年9月25日 下午3:47:29
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String getWebRoot(final HttpServletRequest request){
    return request.getSession().getServletContext().getRealPath(File.separator);
  }

  /**
   * 获取访问项目的网站域名,如http://api.yinlz.com
   * @param request
   * @作者 田应平
   * @返回值类型 String
   * @创建时间 2016年1月16日 15:18:55
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String getDomainName(final HttpServletRequest request){
    return request.getScheme()+"://"+request.getServerName();
  }

  /**
   * 统计处理
   * @作者 田应平
   * @参数 List 是数据的数据条数
   * @参数 keyTotal是count字段或该字段别名
   * @参数 decimalFormat是统计时的数据格式化,如0、0.0、0.00
   * @创建时间 2016年9月12日 下午7:34:01
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static List<Map<String, Object>> statistics(final List<Map<String, Object>> list,final String keyTotal,final String decimalFormat){
    int total = 0;
    for(int i = 0; i < list.size(); i++){
      final Map<String, Object> map = list.get(i);
      for(final String key : map.keySet()){
        if(key.equals(keyTotal)){
          total += Integer.parseInt(map.get(key).toString());//计算总数
        }
      }
    }
    final List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
    for(int i = 0; i < list.size(); i++){
      final Map<String, Object> map = list.get(i);
      final Map<String, Object> m = new HashMap<String, Object>();
      for(final String key : map.keySet()){
        if(key.equals(keyTotal)){
          float f = (float)(Integer.parseInt(map.get(key).toString()))/total * 100;//求平均数
          final DecimalFormat df = new DecimalFormat(decimalFormat);//格式化小数,如0.0或0或0.00
          m.put(key,Double.parseDouble(df.format(f)));
        }else {
          m.put(key,map.get(key));
        }
      }
      result.add(m);
    }
    return result;
  }

  /**
   * 生成带分页的参数查询参数
   * @param params
   * @param pageSize 每页大小
   * @param current 当前页
   * @作者 田应平
   * @返回值类型 HashMap<String,Object>
   * @创建时间 2016年12月29日 下午10:06:03
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static HashMap<String, Object> pageParams(final HashMap<String, Object> params,final Integer pageSize,final Integer current){
    params.put(ConfigFile.section,(current - 1) * pageSize);//读取区间
    params.put(ConfigFile.pageSize,pageSize);//每页大小
    return params;
  }

  /**
   * 数据库为Mysql的datatable组装固定分页参数,section读取区间;pageSize每页大小;
   * @作者 田应平
   * @返回值类型 PageFormData
   * @创建时间 2017年1月23日 下午3:45:06
   * @QQ号码 444141300
   * @主页 http://www.fwtai.com
  */
  public static PageFormData dataTableMysql(final PageFormData formData,final String... keys) throws Exception{
    final String iDisplayLength = "iDisplayLength";
    final String iDisplayStart = "iDisplayStart";
    Integer rows = formData.getInteger(iDisplayLength);
    final Integer start = formData.getInteger(iDisplayStart);
    if(rows == null || start == null) return null;
    int current = (start / rows) + 1; //计算当前页
    final String sort = formData.getString("sSortDir_0");//排序时,排序关键字,asc|desc
    final String index = formData.getString("iSortCol_0");//排序时,排序的列索引
    final String column = formData.getString("mDataProp_"+index);//排序的字段
    formData.remove(iDisplayLength);
    formData.remove(iDisplayStart);
    if(keys.length > 0){
      for(int i = keys.length - 1; i >= 0; i--){
        final String key = keys[i];
        if(!formData.containsKey(key)){
          return null;
        };
      }
    }
    if(sort != null){
      formData.remove("sSortDir_0");
      formData.remove("iSortCol_0");
      formData.remove("mDataProp_"+index);
    }
    if(rows < 0){
      rows = 50;
    }
    if(current <= 0){
      current = 1;
    }
    final int pageSize = rows > 100 ? ConfigFile.size_default : rows;
    formData.put(ConfigFile.section,(current - 1) * pageSize);//读取区间
    formData.put(ConfigFile.pageSize,pageSize);//每页大小
    if(sort != null && column != null){
      final boolean orderKey = ToolString.checkOrderKey(sort);
      final String _column = orderKey ? ToolString.sqlInject(column) : null;
      final String _sort = orderKey ? ToolString.sqlInject(sort) : null;
      if(_column != null && _sort != null){
        formData.put("column",_column);//排序字段 order by name desc
        formData.put("order",_sort);//排序关键字(升序|降序)
      }
    }
    return formData;
  }

  /**
   * 获取表单的请求参数,不含文件域,返回的是HashMap<String,String>
   * @param request
   * @作者:田应平
   * @创建时间 2019年11月13日 19:14:15
   * @主页 www.fwtai.com
  */
  public static HashMap<String,String> getFormParams(final HttpServletRequest request){
    final HashMap<String,String> params = new HashMap<String,String>();
    abstractMap(request,params);
    return params;
  }

  /**
   * 获取表单的请求参数,不含文件域,返回的是线程安全的ConcurrentHashMapString,String>
   * @param request
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2019/11/13 19:29
  */
  public static ConcurrentHashMap<String,String> getFormParam(final HttpServletRequest request){
    final ConcurrentHashMap<String,String> params = new ConcurrentHashMap<String,String>();
    abstractMap(request,params);
    return params;
  }

  protected static void abstractMap(final HttpServletRequest request,final AbstractMap<String,String> map){
    final Enumeration<String> paramNames = request.getParameterNames();
    while(paramNames.hasMoreElements()){
      final String key = paramNames.nextElement();
      if(key.equals("_"))continue;
      String value = request.getParameter(key);
      if(value != null && value.length() >0){
        value = value.trim();
        if(checkNull(value))
          continue;
        map.put(key,value);
      }
    }
  }

  /**
   * 获取表单的请求参数,不含文件域
   * @param request
   * @作者:田应平
   * @创建时间 2017年10月21日 16:03:16
   * @主页 www.fwtai.com
  */
  public static PageFormData getFormData(final HttpServletRequest request){
    return new PageFormData(request);
  }

  public static boolean checkNull(final String value){
    if(value == null) return true;
    if(value.length() == 0)return true;
    if(value.equals("_"))return true;
    if(value.equalsIgnoreCase("undefined"))return true;
    return value.equalsIgnoreCase("null");
  }

  /**
   * 获取由HttpClient发送的数据的HttpServletRequest请求参数
   * @作者 田应平
   * @QQ 444141300
   * @param request 请求参数,默认的字符编码为"UTF-8"
   * @创建时间 2018年7月3日 09:33:19
  */
  public static String getHttpClientRequest(final HttpServletRequest request) throws IOException {
    final StringBuilder sb = new StringBuilder();
    final InputStream is = request.getInputStream();
    final InputStreamReader isr = new InputStreamReader(is,"UTF-8");
    final BufferedReader br = new BufferedReader(isr);
    String s = "";
    while ((s = br.readLine()) != null){
      sb.append(s);
    }
    return sb.length() > 0 ? sb.toString() : null;
  }

  /**
   * 获取由HttpClient发送的数据的HttpServletRequest请求参数
   * @作者 田应平
   * @QQ 444141300
   * @param request 请求参数
   * @param charsetName 字符编码,如 "UTF-8"
   * @创建时间 2018年7月3日 09:39:00
  */
  public static String getHttpClientRequest(final HttpServletRequest request,final String charsetName) throws IOException {
    final StringBuilder sb = new StringBuilder();
    final InputStream is = request.getInputStream();
    final InputStreamReader isr = new InputStreamReader(is,charsetName);
    final BufferedReader br = new BufferedReader(isr);
    String s = "";
    while ((s = br.readLine()) != null){
      sb.append(s);
    }
    return sb.length() > 0 ? sb.toString() : null;
  }

  /**
   * 返回没有操作权限的code=401,msg=没有操作权限
   * @param
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020/3/1 0:13
  */
  public static String accessDenied(){
    return createJson(ConfigFile.code401,ConfigFile.msg401);
  }

  /**
   * token无效或已过期,请重新登录
   * @param
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020/4/19 21:00
  */
  public static String tokenInvalid(){
    return tokenInvalid(ConfigFile.token_invalid);
  }

  public static String tokenInvalid(final String msg){
    return createJson(ConfigFile.code205,msg);
  }

  /**
   * 未登录提示信息,json格式,code=205
   * @作者 田应平
   * @创建时间 2017年1月14日 上午12:46:08
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String accessLogin(){
    return createJson(ConfigFile.code205,ConfigFile.token_invalid);
  }

  /**
   * 账号或密码错误
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020/5/1 0:16
  */
  public static String invalidUserInfo(){
    return createJson(ConfigFile.code206,ConfigFile.msg206);
  }

  /**
   * 用于生成Easyui里的datagrid数据表格
   * @param total 总条数|总记录数
   * @param listData 数据库返回的list集合数据
   * @作者 田应平
   * @返回值类型 String
   * @创建时间 2020年3月22日 22:51:46
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String dataTableOK(final List<?> listData,final Object total){
    final JSONObject json = new JSONObject(5);
    if(listData.isEmpty()){
      json.put(ConfigFile.code,ConfigFile.code201);
      json.put(ConfigFile.msg,ConfigFile.msg201);
      return json.toJSONString();
    }else{
      json.put(ConfigFile.data,listData);
      longFilter(json);//上一行put对应的key和value,所以需要处理list里的元素含有Long类型
      json.put(ConfigFile.code,ConfigFile.code200);
      json.put(ConfigFile.msg,ConfigFile.msg200);
      json.put(ConfigFile.recordsTotal,total);
      json.put(ConfigFile.recordsFiltered,total);
      return json.toJSONString();
    }
  }

  /**
   * 用于生成Easyui里的datagrid数据表格,后面新写的代码均不再使用此方法
   * @param total 总条数|总记录数
   * @param listData 数据库返回的list集合数据
   * @作者 田应平
   * @返回值类型 String
   * @创建时间 2020年3月22日 22:51:46
   * @QQ号码 444141300
   * @官网 http://www.fwtai.com
  */
  public static String dataTableOK(final List<?> listData,final Object total,final Object attach){
    final JSONObject json = new JSONObject(6);
    if(listData.isEmpty()){
      json.put(ConfigFile.code,ConfigFile.code201);
      json.put(ConfigFile.msg,ConfigFile.msg201);
      return json.toJSONString();
    }else{
      if(attach != null){
        json.put(ConfigFile.attach,attach);//用法:drawCallback : function(settings){var attach = settings.jqXHR.responseJSON.attach || [];console.log(attach);},参考 wine-frontend/src/main/resources/static/proxy.html
      }
      json.put(ConfigFile.data,listData);
      longFilter(json);//上一行put对应的key和value,所以需要处理list里的元素含有Long类型
      json.put(ConfigFile.code,ConfigFile.code200);
      json.put(ConfigFile.msg,ConfigFile.msg200);
      json.put(ConfigFile.recordsTotal,total);
      json.put(ConfigFile.recordsFiltered,total);
      return json.toJSONString();
    }
  }

  /**把json里含有Long类型且长度大于15个数字的处理成String*/
  private static void longFilter(final JSONObject json){
    json.valueFilter((object,key,value) -> {
      if(value instanceof Long){
        final String str = String.valueOf(value);
        if(str.length() > 15){
          return str;
        }
      }
      return value;
    });
  }

  public static String dataTableException(){
    final JSONObject json = new JSONObject(6);
    json.put(ConfigFile.code,ConfigFile.code204);
    json.put(ConfigFile.msg,ConfigFile.msg204);
    json.put(ConfigFile.recordsTotal,0);
    json.put(ConfigFile.recordsFiltered,0);
    json.put(ConfigFile.data,new ArrayList<Object>());
    return json.toJSONString();
  }

  public static String dataTableException(final Throwable exception){
    logger.error(exception.getMessage());
    return dataTableException();
  }

  //后面新写的代码均不再使用此方法
  public static String dataTableException(final Object sEcho){
    final JSONObject json = new JSONObject(6);
    json.put(ConfigFile.code,ConfigFile.code204);
    json.put(ConfigFile.msg,ConfigFile.msg204);
    json.put("sEcho",sEcho);
    json.put(ConfigFile.recordsTotal,0);
    json.put(ConfigFile.recordsFiltered,0);
    json.put(ConfigFile.data,new ArrayList<Object>());
    return json.toJSONString();
  }

  public static String dataTableException(final String json){
    final JSONObject object = new JSONObject(6);
    object.put(ConfigFile.code,ConfigFile.code204);
    object.put(ConfigFile.msg,JSONObject.parseObject(json,JSONObject.class).get(ConfigFile.msg));
    object.put(ConfigFile.recordsTotal,0);
    object.put(ConfigFile.recordsFiltered,0);
    object.put(ConfigFile.data,new ArrayList<String>(0));
    return object.toJSONString();
  }

  public static String dataTableException(final String json,final Throwable exception){
    logger.error(exception.getMessage());
    return dataTableException(json);
  }

  /**获取带分页的数据,适用于移动端或小程序,因为限制了每页大小最大为20,返回null时说明参数有误!!然后再判断某个key是否必填*/
  public static PageFormData pageParamsApi(final HttpServletRequest request,final String... keys){
    try {
      final PageFormData params = new PageFormData(request);
      if(keys.length > 0){
        for(int i = keys.length - 1; i >= 0; i--){
          final String key = keys[i];
          if(!params.containsKey(key)){
            return null;
          }
        }
      }
      final String rows = params.getString("pageSize");//每页大小
      final String current = params.getString("current");//当前页
      if(current == null || rows == null) return null;
      final String order = params.getString("order");//排序关键字
      final String column = params.getString("column");//排序字段
      int currentPage = Integer.parseInt(current);
      int pageSize = Integer.parseInt(rows);
      if(pageSize < 1 || pageSize > ConfigFile.size_api_default) pageSize = 10;
      if(currentPage < 1)currentPage = 1;
      params.remove("pageSize");
      params.remove("current");
      params.put(ConfigFile.section,(currentPage - 1) * pageSize);//读取区间
      params.put(ConfigFile.pageSize,pageSize);//每页大小
      params.remove("order");
      params.remove("column");
      if(order != null && column != null){
        final boolean orderKey = ToolString.checkOrderKey(order);
        final String _column = orderKey ? ToolString.sqlInject(column) : null;
        final String _sort = orderKey ? ToolString.sqlInject(order) : null;
        if(_sort != null && _column != null){
          params.put("order",_sort);//排序关键字
          params.put("column",_column);//排序的字段 order by name desc
        }
      }
      return params;
    } catch (final Exception e){
      return null;
    }
  }

  /**响应客户端,返回分页数据,含总条数*/
  public static String jsonPage(final List<?> listData,final Integer total){
    final JSONObject json = new JSONObject(5);
    if(listData == null || listData.size() == 0 || total == 0){
      return queryEmpty();
    }else{
      json.put(ConfigFile.data,listData);
      longFilter(json);
      json.put(ConfigFile.code,ConfigFile.code200);
      json.put(ConfigFile.msg,ConfigFile.msg200);
      json.put(ConfigFile.total,total);
      json.put(ConfigFile.record,listData.size());
      return json.toJSONString();
    }
  }

  /**响应客户端,返回分页数据,适用于移动端或小程序,无总条数*/
  public static String jsonPage(final List<?> listData){
    final JSONObject json = new JSONObject(4);
    if(listData == null || listData.size() == 0){
      return queryEmpty();
    }else{
      json.put(ConfigFile.data,listData);
      longFilter(json);
      json.put(ConfigFile.code,ConfigFile.code200);
      json.put(ConfigFile.msg,ConfigFile.msg200);
      json.put(ConfigFile.record,listData.size());
      return json.toJSONString();
    }
  }

  /**适用于List里含有list,前端仅需判断record是否需要下一页*/
  public static String jsonPage(final HashMap<String,Object> data,final Integer record){
    final JSONObject json = new JSONObject(3);
    if(data.isEmpty() || record == 0){
      return queryEmpty();
    }else{
      json.put(ConfigFile.data,data);
      longFilter(json);
      json.put(ConfigFile.code,ConfigFile.code200);
      json.put(ConfigFile.record,record);
      return json.toJSONString();
    }
  }

  /**
   * 封装文件上传,返回值实体 UploadObject,仅需判断 uploadObject.getErrorMsg();是否为空再处理业务
   * @param baseDir 该值的结尾必须要带 /
   * @param prefixType 前缀分类,如图片 /images; 或 /excel/import
   * @param temporary 文件是否是临时文件,如导入的文件算是临时的文件,是临时则不创建以日期的生成的目录
   * @param limitCount 如果该值为null或为负数时则不限制文件数
   * @param limitMb 限制单个文件大小
   * @param verify 文件是否是必填项
   * @param allowExt 限制接收的后缀文件名，带.的后缀名
   * @return HashMap_key,Object>,其中key可能为error,files,params，要做error判断再做页面处理,若 key 不为空时,那files则是　ArrayList_HashMap_String,String;params是PageFormData
   * @注意事项 todo 必须判断 final ArrayList<UploadFile> listFiles = uploadObject.getListFiles();if(listFiles != null){循环取值处理}
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020年6月1日 21:04:37
  */
  @Deprecated
  public static UploadObject uploadFile(final HttpServletRequest request,final String baseDir,final String prefixType,final boolean temporary,final Integer limitCount,final Integer limitMb,final boolean verify,final String... allowExt){
    final UploadObject uploadObject = new UploadObject();
    MultipartHttpServletRequest mhsr = null;
    try {
      mhsr = (MultipartHttpServletRequest) request;
    } catch (Exception e){
      if(verify){
        uploadObject.setErrorMsg("请上传文件");
        return uploadObject;
      }
    }
    if(mhsr == null){
      if(verify){
        uploadObject.setErrorMsg("参数有误或未选择文件");//swagger在调用时可能会有bug,用Postman没有问题!这个是swagger的bug!!!
        return uploadObject;
      }else{
        return uploadObject;
      }
    }
    final Map<String,MultipartFile> map = mhsr.getFileMap();
    if(verify){
      if(map == null || map.size() == 0){
        uploadObject.setErrorMsg("请选择上传文件");
        return uploadObject;
      }
    }
    if(limitCount != null && limitCount > 0){
      if(map.size() > limitCount){
        uploadObject.setErrorMsg("文件数量已超过限制");
        return uploadObject;
      }
    }
    final DiskFileItemFactory fac = new DiskFileItemFactory();
    final ServletFileUpload upload = new ServletFileUpload(fac);
    final ArrayList<UploadFile> files = new ArrayList<UploadFile>();
    try {
      upload.setHeaderEncoding("utf-8");
      mhsr.setCharacterEncoding("utf-8");
      for(final String key : map.keySet()){
        final MultipartFile mf = mhsr.getFile(key);
        if(mf.getSize() > limitMb*1024*1024L){//限制单个文件大小,必须结合配置文件 spring.servlet.multipart.max.xxx一起使用
          for(int i = 0; i < files.size(); i++){
            delFileByThread(files.get(i).getFullPath());
          }
          uploadObject.setErrorMsg("操作失败,某个文件过大");
          return uploadObject;
        }
        final String originalName = mf.getOriginalFilename();
        final int beginIndex = originalName.lastIndexOf(".");//因为Linux下的可执行文件可能没有后缀名的
        if(beginIndex == -1){
          for(int i = 0; i < files.size(); i++){
            delFileByThread(files.get(i).getFullPath());
          }
          uploadObject.setErrorMsg("非法的文件格式");
          return uploadObject;
        }
        final String extName = originalName.substring(beginIndex);
        if(allowExt.length > 0){
          final boolean verifySuffix = Arrays.stream(allowExt).anyMatch((item) -> {
            return item.equalsIgnoreCase(extName);
          });
          if(!verifySuffix){
            for(int i = 0; i < files.size(); i++){
              delFileByThread(files.get(i).getFullPath());
            }
            uploadObject.setErrorMsg("不支持的文件格式");
            return uploadObject;
          }
        }
        if(checkSuffix(extName)){
          for(int i = 0; i < files.size(); i++){
            delFileByThread(files.get(i).getFullPath());
          }
          uploadObject.setErrorMsg("不支持的文件格式");
          return uploadObject;
        }
        final String fileWithoutSuffix = String.valueOf(new IdBuilder().getId121L());
        final String fileName = fileWithoutSuffix + extName;
        final String dayDir = temporary ? (prefixType + "/") : (prefixType + "/" + DateTimeFormatter.ofPattern("yyyyMMdd").format(ZonedDateTime.now()) + "/");
        final File fileDir = new File(baseDir + dayDir);
        if(!fileDir.exists()){
          fileDir.mkdirs();
        }
        final String fullPath = (baseDir + dayDir + fileName).replaceAll("//","/");
        mf.transferTo(FileSystems.getDefault().getPath(fullPath));
        final UploadFile uploadFile = new UploadFile();
        uploadFile.setUrlFile(dayDir + fileName);// Nginx的配置windows环境的路径 root C:\\;上传的跟目录是 C:\images; Nginx的配置linux的环境路径 root /home/data/;上传的跟目录是 /home/data/images/
        uploadFile.setOriginalName(originalName);
        uploadFile.setFullPath(fullPath);
        uploadFile.setFileName(fileName);
        uploadFile.setFileDir(dayDir);
        uploadFile.setFileWithoutSuffix(fileWithoutSuffix);
        uploadFile.setBasePath(baseDir);
        uploadFile.setName(key);
        files.add(uploadFile);
      }
      if(files.size() > 0){
        uploadObject.setListFiles(files);
      }
      final PageFormData formData = new PageFormData(request);
      if(formData.size() > 0){
        uploadObject.setParams(formData);
      }
      return uploadObject;
    } catch (final Exception e) {
      logger.error("uploadFile上传失败,"+e.getMessage());
      for(int i = 0; i < files.size(); i++){
        delFileByThread(files.get(i).getFullPath());
      }
      uploadObject.setErrorMsg("操作失败,处理文件失败");
      return uploadObject;
    }
  }

  /**
   * 封装文件上传,返回值实体 UploadObject,仅需判断 uploadObject.getErrorMsg();是否为空再处理业务,分片上传,推荐 https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&mid=2247505123&idx=1&sn=97fa6b5794b828e330ca0ddf4b6df8f5
   * @param baseDir 该值的结尾必须要带 /
   * @param prefixType 前缀分类,如图片 /images; 或 /excel/import
   * @param temporary 文件是否是临时文件,如导入的文件算是临时的文件,是临时则不创建以日期的生成的目录
   * @param limitCount 如果该值为null或为负数时则不限制文件数
   * @param limitMb 限制单个文件大小
   * @param required 文件是否是必填项的字段
   * @param allowExt 限制接收的后缀文件名，带.的后缀名
   * @return HashMap_key,Object>,其中key可能为error,files,params，要做error判断再做页面处理,若 key 不为空时,那files则是　ArrayList_HashMap_String,String;params是PageFormData
   * @注意事项 todo 必须判断 final ArrayList<UploadFile> listFiles = uploadObject.getListFiles();if(listFiles != null){循环取值处理}
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020年6月1日 21:04:37
  */
  public static UploadObject uploadFile(final HttpServletRequest request,final String baseDir,final String prefixType,final boolean temporary,final Integer limitCount,final Integer limitMb,final String[] required,final String... allowExt){
    final UploadObject uploadObject = new UploadObject();
    final MultipartHttpServletRequest mhsr = (MultipartHttpServletRequest) request;
    final boolean verify = required != null;
    if(mhsr == null){
      if(verify){
        uploadObject.setErrorMsg("参数有误或未选择文件");//swagger在调用时可能会有bug,用Postman没有问题!这个是swagger的bug???
        return uploadObject;
      }else{
        return uploadObject;//此行不能删除,上传的文件可能允许为空
      }
    }
    final Map<String,MultipartFile> map = mhsr.getFileMap();
    if(verify){
      if(map.size() == 0){
        uploadObject.setErrorMsg("请选择上传文件");
        return uploadObject;
      }
    }
    if(limitCount != null && limitCount > 0){
      if(map.size() > limitCount){
        uploadObject.setErrorMsg("文件数量已超过限制");
        return uploadObject;
      }
    }
    if(verify){
      for(int x = required.length - 1; x >= 0; x--){
        final String item = required[x];
        final boolean b = map.keySet().stream().anyMatch(element-> element.startsWith(item));
        if(!b){
          uploadObject.setErrorMsg("缺少文件未上传，请检查");
          return uploadObject;
        }
      }
    }
    for (final Map.Entry<String,MultipartFile> entry : map.entrySet()){
      final MultipartFile mf = entry.getValue();
      final String originalName = mf.getOriginalFilename();
      if(mf.getSize() > limitMb*1024*1024L){
        uploadObject.setErrorMsg("文件["+originalName+"]已大于"+limitMb+"MB");
        return uploadObject;
      }
      final int beginIndex = originalName.lastIndexOf(".");//因为Linux下的可执行文件可能没有后缀名的
      if(beginIndex == -1){
        uploadObject.setErrorMsg("非法的文件格式");
        return uploadObject;
      }
      final String extName = originalName.substring(beginIndex);
      if(allowExt.length > 0){
        final boolean verifySuffix = Arrays.stream(allowExt).anyMatch((item) -> item.equalsIgnoreCase(extName));
        if(!verifySuffix){
          uploadObject.setErrorMsg("不支持的文件格式");
          return uploadObject;
        }
      }
      if(checkSuffix(extName)){
        uploadObject.setErrorMsg("不支持的文件类型");
        return uploadObject;
      }
    }
    final ArrayList<UploadFile> files = new ArrayList<UploadFile>(map.size());
    try {
      mhsr.setCharacterEncoding("UTF-8");
      for (final Map.Entry<String,MultipartFile> entry : map.entrySet()){
        final String key = entry.getKey();
        final MultipartFile mf = entry.getValue();
        final String originalName = mf.getOriginalFilename();
        final int beginIndex = originalName.lastIndexOf(".");//获取后缀名
        final String extName = originalName.substring(beginIndex);
        final long pointPrefix = new IdBuilder().getId121L();
        final String fileName = pointPrefix + extName;
        final String separator = "/";
        final String dayDir = temporary ? (prefixType + separator) : (prefixType + separator + DateTimeFormatter.ofPattern("yyyyMMdd").format(ZonedDateTime.now()) + separator);
        final String currentDir = baseDir + dayDir;
        final Path path = FileSystems.getDefault().getPath(currentDir);//nio
        final boolean fileDir = Files.exists(path);//nio
        if(!fileDir){
          Files.createDirectories(path);//创建目录,nio
        }
        final String fullPath = currentDir + fileName;
        mf.transferTo(FileSystems.getDefault().getPath(fullPath));//nio
        final UploadFile uploadFile = new UploadFile();
        uploadFile.setUrlFile(dayDir + fileName);// Nginx的配置windows环境的路径 root C:\\;上传的跟目录是 C:\images; Nginx的配置linux的环境路径 root /home/data/;上传的跟目录是 /home/data/images/
        uploadFile.setOriginalName(originalName);
        uploadFile.setFullPath(fullPath);
        uploadFile.setFileName(fileName);
        uploadFile.setFileDir(dayDir);
        uploadFile.setFileWithoutSuffix(String.valueOf(pointPrefix));
        uploadFile.setBasePath(baseDir);
        uploadFile.setCurrentDir(currentDir);
        uploadFile.setName(key);
        files.add(uploadFile);
      }
      if(files.size() > 0){
        uploadObject.setListFiles(files);
      }
      final PageFormData formData = new PageFormData(request);
      if(formData.size() > 0){
        uploadObject.setParams(formData);
      }
      return uploadObject;
    } catch (final Exception e) {
      logger.error("uploadFile上传失败,"+e.getMessage());
      for(int i = files.size() - 1; i >= 0; i--){
        delFileByThread(files.get(i).getFullPath());
      }
      uploadObject.setErrorMsg("操作失败,处理文件失败");
      return uploadObject;
    }
  }

  /**限制常用的可执行文件*/
  private static boolean checkSuffix(final String value){
    final String[] suffixs = new String[]{".js",".sh",".jsp",".jspx",".html",".htm",".java",".class",".h",".c",".jar",".war"};
    return Arrays.stream(suffixs).anyMatch(item->{
      return item.equalsIgnoreCase(value);
    });
  }

  /**
   * 开线程访问服务器删除图片
   * @date 2019年10月31日 16:41:40
  */
  public static void delFileByThread(final String filePath) {
    final ExecutorService threads = Executors.newCachedThreadPool();
    threads.execute(new Runnable(){
      @Override
      public void run(){
        try {
          ToolFile.delete(filePath);
        } catch (final Exception ignored){
        }finally{
          threads.shutdown();//释放线程池
        }
      }
    });
  }

  /**获取文件后缀名*/
  public static String getSuffix(final String originalName){
    return originalName.substring(originalName.lastIndexOf("."));
  }

  /**
   * 把URL后跟的查询字符串转成HashMap对象
  */
  public static HashMap<String,String> parseQuery(final String urlQuery) {
    final HashMap<String,String> data = new HashMap<String,String>();
    final String[] params = urlQuery.split("&");
    for (final String param : params) {
      final String[] k = param.split("=");
      final String value = k[1];
      if(value != null && value.trim().length() > 0){
        data.put(k[0],value);
      }
    }
    return data;
  }

  /**获取访问者真实的IP地址*/
  public static String getIp(final HttpServletRequest request){
    String ip = request.getHeader("x-forwarded-for");
    if(ip == null || ip.trim().length() == 0 || "unknown".equalsIgnoreCase(ip)){
      ip = request.getHeader("Proxy-Client-IP");
    }
    if(ip == null || ip.trim().length() == 0 || "unknown".equalsIgnoreCase(ip)){
      ip = request.getHeader("WL-Proxy-Client-IP");
    }
    if(ip == null || ip.trim().length() == 0 || "unknown".equalsIgnoreCase(ip)){
      ip = request.getRemoteAddr();
    }
    if (ip != null && ip.length() > 0){
      if(ip.contains(",")){
        String[] ips = ip.split(ip);
        if(ips.length > 0){
          return ips[0];
        }
      }
    }
    return ip;
  }

  /**获取访问者真实的IP地址*/
  public static String getIpAddr(final HttpServletRequest request)
    throws Exception {
    if (request == null) {
      throw (new Exception("getIpAddr method HttpServletRequest Object is null"));
    }
    String ipString = request.getHeader("x-forwarded-for");
    if (ipString == null || ipString.trim().length() == 0 || "unknown".equalsIgnoreCase(ipString)) {
      ipString = request.getHeader("Proxy-Client-IP");
    }
    if (ipString == null || ipString.trim().length() == 0 || "unknown".equalsIgnoreCase(ipString)) {
      ipString = request.getHeader("WL-Proxy-Client-IP");
    }
    if (ipString == null || ipString.trim().length() == 0 || "unknown".equalsIgnoreCase(ipString)) {
      ipString = request.getRemoteAddr();
    }
    // 多个路由时，取第一个非unknown的ip
    final String[] arr = ipString.split(",");
    for (final String str : arr) {
      if (!"unknown".equalsIgnoreCase(str)) {
        ipString = str;
        break;
      }
    }
    return ipString;
  }

  /**
   * 已实现 implements Serializable 的实体转PageFormData
   * @param cls
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020/12/16 11:32
  */
  public static PageFormData beanToPageFormData(final Serializable cls){
    return JSONObject.parseObject(JSON.toJSONString(cls),PageFormData.class);
  }

  /**
   * 指定日期时间或日期格式转为long
   * @param map 的key直接指定前端传递来的日期时间格式(yyyy-MM-dd HH:mm:ss;yyyy-MM-dd;yyyy-MM-dd HH:mm)
   * @用法: final HashMap<String,String[]> map = new HashMap<>(2);
   * map.put("yyyy-MM-dd HH:mm:ss",new String[]{"add_time","update_time"});
   * map.put("yyyy-MM-dd",new String[]{"edit_time"});
   * ToolClient.dateToLong(formData,map);
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2022/12/13 17:11
  */
  public static void dateToLong(final PageFormData formData,final HashMap<String,String[]> map){
    if(map.size() > 0){
      for(final String pattern : map.keySet()){
        final String[] arrs = map.get(pattern);
        if(arrs.length >0){
          for(int x = arrs.length - 1; x >= 0; x--){
            final String k = arrs[x];
            final String o = (String)formData.get(k);
            if(o != null){
              try {
                formData.put(k,new SimpleDateFormat(pattern).parse(o).getTime() / 1000);
              } catch (final ParseException ignored) {}
            }
          }
        }
      }
    }
  }

  public static String toJson(final Map<String, Object> map){
    return new JSONObject(map).toJSONString();
  }
}